package stock.fish.util;

import com.alibaba.fastjson.JSONArray;
import com.google.common.collect.ImmutableMap;
import jdk.internal.org.objectweb.asm.Handle;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.sql.Array;
import java.util.*;

/**
 * @ClassName: EsTest
 * @Description: 烤鳗鱼算法测试
 * @Author yxl
 * @Date 2024/11/8
 */
//@SpringBootTest
@Slf4j
public class FishTest {

    // 基数池，当满x个时开始匹配，最低10个
    private static Integer MIN_FISH_POOL_COUNT = 30;
    // 基数池，当满x个时停止操作
    private static Integer MAX_FISH_POOL_COUNT = 60;
    private static List<Integer> FISH_POOL = new ArrayList<>();
    // 打包的鳗鱼个数
    private static List<Integer> FISH_RANGE = Arrays.asList(10, 11);
    // 鳗鱼批次最大和最小重量
    private static Integer MAX_WEIGHT = 565;
    private static Integer MIN_WEIGHT = 446;
    // 目标最小总重量,允许的最大浮动重量
    private static Integer TARGET_WEIGHT = 6000;
    private static Integer FLOATING_VALUE = 100;
    private static Integer MAX_TARGET_WEIGHT = TARGET_WEIGHT + FLOATING_VALUE;

    /**
     * 动态规划
     */
    public static void main(String[] args) throws InterruptedException {
        // 模拟工作缓冲带
        if (MIN_FISH_POOL_COUNT.compareTo(30) < 0) {
            MIN_FISH_POOL_COUNT = 30;
        }
        do {
            // 缓冲带上的鱼数量不满足要求，添加鱼
            if (FISH_POOL.size() < MIN_FISH_POOL_COUNT) {
                addFish();
                continue;
            }
            // 找寻符合要求的鳗鱼重量
            log.info("缓冲带鳗鱼数量达到或超过{}条:{}，开始筛选", MIN_FISH_POOL_COUNT, JSONArray.toJSONString(FISH_POOL));
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            Map<String, Object> chooseResultMap = findClosestSum(FISH_POOL, FISH_RANGE, TARGET_WEIGHT, MAX_TARGET_WEIGHT);
            stopWatch.stop();
            // 匹配失败，添加鱼
            if (chooseResultMap == null) {
                log.warn("组合匹配失败，继续添鱼，当前鱼的重量：{}", JSONArray.toJSONString(FISH_POOL));
                log.warn("消耗：{}ms", stopWatch.getLastTaskTimeMillis());
                addFish();
                continue;
            }
            // 匹配成功
            List<Integer> result = (List<Integer>) chooseResultMap.get("result");
            List<Integer> resultIndex = (List<Integer>) chooseResultMap.get("resultIndex");
            Integer resultWeight = (Integer) chooseResultMap.get("resultWeight");
            log.info("组合匹配成功，{}条鱼，索引位置：{}，重量分别是：{}，合计总重量：{}g", result.size(), JSONArray.toJSONString(resultIndex),
                    JSONArray.toJSONString(result), resultWeight);
            log.info("消耗：{}ms", stopWatch.getLastTaskTimeMillis());
            // 删除缓冲带的鱼
            for (int i = resultIndex.size() - 1; i >= 0; i--) {
                FISH_POOL.remove(resultIndex.get(i).intValue());
            }
        } while (FISH_POOL.size() < MAX_FISH_POOL_COUNT);

        log.error("警告，缓冲带的鳗鱼数量达到最大值{}条：{}，停止工作", MAX_FISH_POOL_COUNT, JSONArray.toJSONString(FISH_POOL));
    }

    private static void addFish() {
        Integer fishWeight = MIN_WEIGHT + (int) (Math.random() * (MAX_WEIGHT - MIN_WEIGHT + 1));
        FISH_POOL.add(fishWeight);
//        Thread.sleep(500);
        log.info("添加鱼，鱼重{}g,当前缓冲带鱼数量{}", fishWeight, FISH_POOL.size());
    }

    private static Map<String, Object> findClosestSum(List<Integer> fishPool, List<Integer> fishRange, Integer targetWeight,
                                                      Integer maxTargetWeight) {
        Integer fishPoolLength = fishPool.size();
        boolean[][] dp = new boolean[fishPoolLength + 1][maxTargetWeight + 1];
        // 初始化dp数组
        for (Integer i = 0; i <= fishPoolLength; i++) {
            dp[i][0] = true;
        }
        //从i(10)个鱼中选择一些数，使得他们的和大于等于j(5000)且小于等于 maxTargetWeight 是否可能
        for (Integer i = 1; i <= fishPoolLength; i++) {
            for (Integer j = 1; j <= maxTargetWeight; j++) {
                if (j >= fishPool.get(i - 1)) {
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - fishPool.get(i - 1)];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        Integer bestWeightSum = 0;
        Integer fishRangIndex = 0;
        List<Integer> result = null;
        List<Integer> resultIndex = null;
        boolean needLoop = true;
        Integer _targetWeight = targetWeight;
        do {
            Integer _fishCount = fishRange.get(fishRangIndex);
            result = new ArrayList<>(_fishCount);
            resultIndex = new ArrayList<>(_fishCount);
            for (Integer integer = 0; integer < _fishCount; integer++) {
                result.add(0);
                resultIndex.add(0);
            }
            // 限制最大范围 maxTargetWeight ，寻找最接近且大于 targetWeight 的和
            for (Integer i = _targetWeight; i <= maxTargetWeight; i++) {
                if (dp[fishPoolLength][i]) {
                    bestWeightSum = i;
                    break;
                }
            }
            if (bestWeightSum.equals(0)) {
                return null;
            }
            // 回溯找到选择的数
            Integer remainingSum = bestWeightSum;
            for (Integer i = fishPoolLength; i > 0 && _fishCount > 0; i--) {
                if (remainingSum >= fishPool.get(i - 1) && dp[i - 1][remainingSum - fishPool.get(i - 1)]) {
                    int k = --_fishCount;
                    result.set(k, fishPool.get(i - 1));
                    resultIndex.set(k, i - 1);
                    remainingSum -= fishPool.get(i - 1);
                }
            }
            // 和数判断
            Integer resultSum = result.stream().mapToInt(Integer::intValue).sum();
            if (resultSum.equals(bestWeightSum) && !result.get(0).equals(0)) {
                needLoop = false;
                return ImmutableMap.of("result", result, "resultIndex", resultIndex, "resultWeight", resultSum);
            } else {
                // 特殊情况下会出现低于最低打包的鱼的数量就满足目标重量，来回转换范围判断总数
                fishRangIndex++;
                if (fishRangIndex >= fishRange.size()) {
                    fishRangIndex = 0;
                    bestWeightSum++;
                }
                if (_targetWeight <= maxTargetWeight) {
                    _targetWeight = bestWeightSum;
                } else {
                    needLoop = false;
                    return null;
                }
            }
        } while (needLoop);
        return null;
    }


}
